package demo.codeanalyzer.processor;

import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;

import com.sun.org.apache.bcel.internal.generic.BALOAD;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;

public class PropertyPrinter {

	private final VariableTree variableTree;

	private final Trees trees;

	private final PrintingVisitor visitor;

	private final Map<String, String> properties;

	private final String nameSpace;

	private String label;

	public PropertyPrinter(VariableTree variableTree, Trees trees,
			PrintingVisitor visitor, Map<String, String> properties,
			String nameSpace) {
		this.variableTree = variableTree;
		this.trees = trees;
		this.visitor = visitor;
		this.properties = properties;
		this.nameSpace = nameSpace;
	}

	public Object print() {
		startPropertyDefinition();
		startSubClassDefinition();
		startRestrictionDefinition();
		printPropertyDefinition();
		printCardinalityDefinition();
		Object result = visitor.visitParentVariable(variableTree, trees);
		endRestrictionDefinition();
		endSubClassDefinition();
		String propertyName = propertyName();
		if (!properties.containsKey(propertyName)) {
			properties.put(propertyName, buildObjectProperty());
		}
		return result;
	}

	private void startSubClassDefinition() {
		System.out.println("<rdfs:subClassOf>");
	}

	private void endSubClassDefinition() {
		System.out.println("</rdfs:subClassOf>\n");
	}

	private void startPropertyDefinition() {
		System.out.println(String.format("<chs:hasProperty %s />",
				resource(propertyName())));
	}

	private void printCardinalityDefinition() {
		writeCardinalityDefinition("mincardinality", "minCardinality");
		writeCardinalityDefinition("cardinality", "cardinality");
		writeCardinalityDefinition("maxcardinality", "maxCardinality");
	}

	private void writeCardinalityDefinition(String matchCardinality,
			String cardinalityTag) {
		String cardinality = cardinality(matchCardinality);
		if (null != cardinality) {
			System.out.println(cardinalityString(cardinality, cardinalityTag));
		}
	}

	private String cardinalityString(String cardinality, String tagName) {
		return String.format(
				"<owl:%s rdf:datatype=\"&xsd;nonNegativeInteger\">%s</owl:%s>",
				tagName, unquote(cardinality), tagName);
	}

	private String cardinality(String matchCardinality) {
		return annotationValue(element(trees), matchCardinality);
	}

	private void printPropertyDefinition() {
		System.out.println(String.format("<owl:onProperty %s />",
				resource(propertyName())));
	}

	private String propertyName() {
		Name name = variableTree.getName();
		return name.toString();
	}

	private void endRestrictionDefinition() {
		System.out.println("</owl:Restriction>");
	}

	private void startRestrictionDefinition() {
		System.out.println("<owl:Restriction>");
	}

	private String buildObjectProperty() {
		StringBuilder objectPropertyBuilder = new StringBuilder();
		PropertyTypeWriter propertyTypeWriter = propertyWriter(type());
		propertyTypeWriter.startPropertyDefinition(objectPropertyBuilder);
		if (null != label) {
			propertyTypeWriter
					.printLabel(objectPropertyBuilder, unquote(label));
		}
		propertyTypeWriter.writeRangeDefinition(objectPropertyBuilder);
		propertyTypeWriter.endPropertyDefinition(objectPropertyBuilder);
		return objectPropertyBuilder.toString();
	}

	private PropertyTypeWriter propertyWriter(String typeString) {
		if (typeString.equalsIgnoreCase("java.lang.string")
				|| typeString.equalsIgnoreCase("int")
				|| typeString.equalsIgnoreCase("java.net.url")
				|| typeString.equalsIgnoreCase("java.util.date")
				|| typeString.equalsIgnoreCase("boolean"))
			return PropertyTypeWriterFactory.createDataProperty(this);
		return PropertyTypeWriterFactory.createObjectProperty(this);
	}

	private void writeRangeDefinition(StringBuilder objectPropertyBuilder) {
		objectPropertyBuilder.append(String.format("<rdf:range %s />\n",
				resource(rangeResource())));
	}

	public String rangeResource() {
		return type();
	}

	String type() {
		return element(trees).asType().toString();
	}

	private void writeDomainDefinition(StringBuilder objectPropertyBuilder) {
		objectPropertyBuilder.append(String.format("<rdf:domain %s />\n",
				resource(domainResource())));
	}

	private String domainResource() {
		return element(trees).getEnclosingElement().getSimpleName().toString();
	}

	public String resource(String resourceType) {
		if (resourceType.startsWith("&"))
			return String.format("rdf:resource=\"%s\"", resourceType);
		return String.format("rdf:resource=\"%s%s\"", nameSpace, resourceType);
	}

	public String id() {
		return String
				.format("rdf:ID=\"%s\"", variableTree.getName().toString());
	}

	protected String annotationValue(Element element, String type) {
		List<? extends AnnotationMirror> annotationMirrors = element
				.getAnnotationMirrors();
		for (AnnotationMirror mirror : annotationMirrors) {
			Name name = mirror.getAnnotationType().asElement().getSimpleName();
			if (type.equals(name.toString().toLowerCase())) {
				return anotationValue(mirror).toString();
			}
		}
		return null;
	}

	private String anotationValue(AnnotationMirror mirror) {
		Collection<? extends AnnotationValue> values = mirror
				.getElementValues().values();
		StringBuffer result = new StringBuffer();
		for (AnnotationValue value : values) {
			result.append(value.toString());
		}
		return result.toString();
	}

	private String unquote(String cardinality) {
		return cardinality.replace("\"", "");
	}

	protected Element element(Trees trees) {
		TreePath currentPath = visitor.getCurrentPath();
		Element element = trees.getElement(currentPath);
		return element;
	}

	public String about() {
		return String.format("rdf:about=\"%s%s\"", nameSpace, propertyName());
	}

	public String rdfType() {
		String type = type();
		if (type.equalsIgnoreCase("int")) {
			return "&xsd;integer";
		}
		if (type.equalsIgnoreCase("boolean")) {
			return "&xsd;boolean";
		}
		if (type.equalsIgnoreCase("java.net.url")) {
			return "&xsd;anyURI";
		}
		if (type.equalsIgnoreCase("java.util.date")) {
			return "&xsd;dateTime";
		}
		return "&xsd;string";
	}

	public void label(String label) {
		this.label = label;
	}

}
